/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package xenon3d.scene;

import javax.media.opengl.GL2;
import xenon3d.vector.Color3f;

/**
 * The Material object defines the appearance of an object under illumination.
 * If the Material object in an Appearance object is null, lighting is disabled
 * for all nodes that use that Appearance object.
 *
 * @author Volker Everts
 * @version 0.1 - 14.11.2011: Created
 */
public class Material extends Attributes {

    // <editor-fold defaultstate="collapsed" desc=" Static Attributes ">

    /** The default material. */
    static final Material DEFAULT = new Material();

    /** The last active material. */
    static Material last = DEFAULT;

    /** Helper array to convert colors to float[]. */
    private static float[] fv = new float[3];

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Private Fields ">

    /** The ambient color of the material. */
    private Color3f ambient = Color3f.AMBIENT;

    /** The diffuse color of the material. */
    private Color3f diffuse = Color3f.DIFFUSE;

    /** The specular color of the material. */
    private Color3f specular = Color3f.WHITE;

    /** The emissive color of the material. */
    private Color3f emissive = Color3f.BLACK;

    /** The shininess of the material. */
    private float shininess = 64.0f;

    /** The lighting flag. */
    private boolean lighting = false;

    /** The color target ambient flag. */
    private boolean colorTargetAmbient = false;

    /** The back face normal flip flag. */
    private boolean backFaceNormalFlip = false;

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Initialization ">

    /**
     * Creates a new Material using default parameter settings. The default
     * setting are:<p>
     * <ul>
     * <li>Ambient color: AMBIENT (0.2, 0.2, 0.2)</li>
     * <li>Diffuse color: DIFFUSE (0.8, 0.8, 0.8)</li>
     * <li>Specular color: WHITE (1.0, 1.0, 1.0)</li>
     * <li>Emissive color: BLACK (0.0, 0.0, 0.0)</li>
     * <li>Shininess: 0.0f (not shiny at all)</li>
     * <li>Lighting: disabled</li>
     * <li>ColorTargetAmbient: disabled</li>
     * <li>BackFaceNormalFlip: disabled</li>
     * </ul>
     */
    public Material() {}

    /**
     * Creates a new Material object using the specified diffuse color and
     * lighting setting.
     * @param color the diffuse material color
     * @param lighting if true, lighting will be enabled for this material
     */
    public Material(Color3f color, boolean lighting) {
        if (color == null) throw new IllegalArgumentException();
        diffuse = color;
        this.lighting = lighting;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Public Properties ">

    /**
     * Sets the ambient color for this material.
     * @param color the new ambient color
     */
    public final void setAmbientColor(Color3f color) {
        checkCapability(Capability.AllowMaterialWrite);
        if (color == null) color = Color3f.AMBIENT;
        ambient = color;
    }

    /**
     * Returns the ambient color for this material.
     * @return the current ambient color
     */
    public final Color3f getAmbientColor() {
        return ambient;
    }

    /**
     * Sets the diffuse color for this material.
     * @param color the new diffuse color
     */
    public final void setDiffuseColor(Color3f color) {
        checkCapability(Capability.AllowMaterialWrite);
        if (color == null) color = Color3f.DIFFUSE;
        diffuse = color;
    }

    /**
     * Returns the diffuse color for this material.
     * @return the current diffuse color
     */
    public final Color3f getDiffuseColor() {
        return diffuse;
    }

    /**
     * Sets the specular color for this material.
     * @param color the new specular color
     */
    public final void setSpecularColor(Color3f color) {
        checkCapability(Capability.AllowMaterialWrite);
        if (color == null) color = Color3f.WHITE;
        specular = color;
    }

    /**
     * Returns the specular color for this material.
     * @return the current specular color
     */
    public final Color3f getSpecularColor() {
        return specular;
    }

    /**
     * Sets the emissive color for this material.
     * @param color the new emissive color
     */
    public final void setEmissiveColor(Color3f color) {
        checkCapability(Capability.AllowMaterialWrite);
        if (color == null) color = Color3f.BLACK;
        emissive = color;
    }

    /**
     * Returns the emissive color for this material.
     * @return the current emissive color
     */
    public final Color3f getEmissiveColor() {
        return emissive;
    }


    /**
     * Sets the shininess for this material.
     * @param shininess the new shininess
     */
    public final void setShininess(float shininess) {
        checkCapability(Capability.AllowMaterialWrite);
        if (shininess < 0.0f) shininess = 0.0f;
        else if (shininess > 128.0f) shininess = 128.0f;
        this.shininess = shininess;
    }

    /**
     * Returns the shininess for this material.
     * @return the current shininess
     */
    public final float getShininess() {
        return shininess;
    }

    /**
     * Sets whether lighting is enabled for this material.
     * @param enable if true, lighting is enabled
     */
    public final void setLighting(boolean enable) {
        checkCapability(Capability.AllowMaterialWrite);
        lighting = enable;
    }

    /**
     * Returns whether lighting is enabled for this material.
     * @return true, if lighting is enabled
     */
    public final boolean isLighting() {
        return lighting;
    }

    /**
     * Sets whether the ambient color target is enabled for this material.
     * @param enable if true, the ambient color target is enabled
     */
    public final void setColorTargetAmbient(boolean enable) {
        checkCapability(Capability.AllowMaterialWrite);
        colorTargetAmbient = enable;
    }

    /**
     * Returns whether the ambient color target is enabled for this material.
     * @return true, if the ambient color target is enabled
     */
    public final boolean isColorTargetAmbient() {
        return colorTargetAmbient;
    }

    /**
     * Sets whether back facing normals are automatically flipped by this
     * material.
     * @param enable if true, back face normal flipping is enabled
     */
    public final void setBackFaceNormalFlip(boolean enable) {
        checkCapability(Capability.AllowMaterialWrite);
        backFaceNormalFlip = enable;
    }

    /**
     * Returns whether back facing normals are automatically flipped by this
     * material.
     * @return true, if back face normal flipping is enabled
     */
    public final boolean isBackFaceNormalFlip() {
        return backFaceNormalFlip;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Package Private Methods (OpenGL) ">

    @Override
    void apply(GL2 gl) {
        if (this == last) return;
        if (colorTargetAmbient != last.colorTargetAmbient) {
            if (colorTargetAmbient) gl.glColorMaterial(GL2.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE);
            else gl.glColorMaterial(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE);
        }
        if (diffuse != last.diffuse) gl.glColor3f(diffuse.r, diffuse.g, diffuse.b);
        if (!colorTargetAmbient && ambient != last.ambient) {
            ambient.get(fv);
            gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, fv, 0);
        }
        if (specular != last.specular) {
            specular.get(fv);
            gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, fv, 0);
        }
        if (emissive != last.emissive) {
            emissive.get(fv);
            gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_EMISSION, fv, 0);
        }
        if (shininess != last.shininess) gl.glMaterialf(GL2.GL_FRONT_AND_BACK, GL2.GL_SHININESS, shininess);
        if (lighting != last.lighting) {
            if (lighting) gl.glEnable(GL2.GL_LIGHTING);
            else gl.glDisable(GL2.GL_LIGHTING);
        }
        if (backFaceNormalFlip != last.backFaceNormalFlip) {
            gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, backFaceNormalFlip ? 1 : 0);
        }
        last = this;
    }

    // </editor-fold>

} // end class Material